#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/mman.h>

#include <sys/ioctl.h>

#include <linux/videodev2.h>

typedef struct	//帧缓冲区
{
	void* start;
	unsigned int length;
}buffer;

static int num = 0;

void process_image(buffer buf)//图像处理  此处可以将采集到的YUV格式的数据进行处理,单帧就是图片,多帧就是视频,对单帧可以转为RGB等格式的图片
{
	printf("===process_image===\n");
	//char buff[255] = {};
	//sprintf(buff,"file%d.yuv",++num);
	//int fd = open(buf,O_RDWR|O_CREAT|O_TRUNC,0777);
	int fd = open("test1.yuv",O_RDWR|O_CREAT|O_APPEND,0777);
	//FILE* file = fopen("file.yuv","wa+");
	write(fd,buf.start,buf.length);
	//fwrite((char*)buf.start,1,buf.length,file);
	close(fd);
	//pause();
}

//查看设备信息
void get_capacity(int fd)
{
	struct v4l2_capability cap;
	ioctl(fd,VIDIOC_QUERYCAP,&cap);
	printf("DriverName:%s\nCard Name:%s\nBus info:%s\nDriverVersion:%u.%u.%u\n",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0xff,(cap.version>>8)&0xff,cap.version&0xff);
}

//查看设备支持的数据格式
void get_fmtdesc(int fd)
{
	struct v4l2_fmtdesc fmtdesc;
	fmtdesc.index=0;
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	printf("Support format:\n");
	while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
	{
		printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
		fmtdesc.index++;
	}
}

//获取当前帧格式
void get_format(int fd)
{
	struct v4l2_format fmt;
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	int ret = ioctl(fd,VIDIOC_G_FMT,&fmt);
	if(ret < 0)
    {
        printf("VIDIOC_G_FMT fail\n");
        return;
    }
	printf("Currentdata format information:\n\twidth:%d\n\theight:%d\n\ttype:%x\n\tpixelformat:%c%c%c%c\n",fmt.fmt.pix.width,fmt.fmt.pix.height,fmt.type,fmt.fmt.pix.pixelformat&0xff,(fmt.fmt.pix.pixelformat>>8)&0xff,(fmt.fmt.pix.pixelformat>>16)&0xff,(fmt.fmt.pix.pixelformat>>24)&0xff);
}

//设置帧格式
void set_format(int fd)
{
    struct v4l2_format format;
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    format.fmt.pix.width = 640;
    format.fmt.pix.height = 480;                   /* 设置摄像头数据的宽和高 */
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  /* 设置为yuyv格式数据 */
    format.fmt.pix.field = V4L2_FIELD_INTERLACED;
    int ret = ioctl(fd, VIDIOC_S_FMT, &format);
    if(ret < 0){
        printf("VIDIOC_S_FMT fail\n");
        return;
    }
}

int main()
{
	unsigned int n_buffers =0;
	int fd = open("/dev/video0",O_RDWR,0);
	//获取设备信息
	get_capacity(fd);
	//显示支持的帧格式
	get_fmtdesc(fd);
	
	//设置帧格式
	set_format(fd);
	//获取确认当前帧格式
	get_format(fd);
	
	//pause();
	/*struct v4l2_fmtdesc fmtdesc;
	fmtdesc.index = 0;
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
	{
		if(fmtdesc.pixelformat& fmt.fmt.pix.pixelformat)
		{
			printf("\tformat:%s\n",fmtdesc.description);
			break;
		}
		fmtdesc.index++;
	}
	*/
	//申请内存缓冲区,并将内存映射到用户空间mmap
	struct v4l2_requestbuffers req;
	req.count = 4;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory = V4L2_MEMORY_MMAP;
	int ret = ioctl(fd,VIDIOC_REQBUFS,&req);
	if(0 != ret)
	{
        printf("VIDIOC_REQBUFS fail\n");
        return -1;
    }
	//查询缓冲区分配的地址,并进行映射
	buffer* buffers = (buffer*)calloc(req.count,sizeof(buffer));
	
	if (!buffers) 
	{
		fprintf (stderr,"Out of memory/n");
		exit(EXIT_FAILURE);
	}
	for (n_buffers = 0; n_buffers < req.count; ++n_buffers) 
	{
		struct v4l2_buffer buf;
		memset(&buf,0,sizeof(buf));//
		buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory =V4L2_MEMORY_MMAP;
		buf.index =n_buffers;
		//获取内存缓冲区的帧
		if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) exit(-1);
		buffers[n_buffers].length= buf.length;
		//将内存缓冲区的帧与应用内存进行映射
		buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
		if (MAP_FAILED == buffers[n_buffers].start) exit(-1);
		
		//放入摄像头帧采集入队队列
		ioctl (fd,VIDIOC_QBUF, &buf);
	}
	//开始视频流的采集
	//unsigned int i; 
	enum v4l2_buf_type type;
	// 
	/*for (i = 0; i< 4; ++i)
	{
		struct v4l2_buffer buf;
		buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory =V4L2_MEMORY_MMAP;
		buf.index = i;
		ioctl (fd,VIDIOC_QBUF, &buf);
	}*/
	//开启视频流
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ioctl (fd,VIDIOC_STREAMON, &type);
	
	int flag = 1;
	while(flag)
	{
		struct v4l2_buffer buf;
		memset(&buf,0,sizeof(buf));//clear
		buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory =V4L2_MEMORY_MMAP;
		//取出缓冲帧
		ioctl (fd,VIDIOC_DQBUF, &buf);
		// 图像处理
		process_image(buffers[buf.index]);
		//放入缓冲帧
		ioctl(fd,VIDIOC_QBUF,&buf);
		usleep(40);
	}
		
	//关闭视频流
	ioctl(fd,VIDIOC_STREAMOFF,&type);
	
	//取消映射unmap

	close(fd);
}

